﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Conversive.Verbot5;


namespace XNAViewerSample
{
    public class Personality
    {
        #region Fields
        private Verbot5Engine verbot;
        private State state;
        private bool quitFlag;

        public enum AIType { UNKNOWN, VERBOT, AIML };
        #endregion

        /// <summary>
        /// Personality class constructor
        /// </summary>
        public Personality()
        {
            try
            {
                verbot = new Verbot5Engine();
                state = new State();
                quitFlag = false;
            }
            catch
            {
                // TODO: exception when unable to create AI
            }
        }

        public AIType EngineType
        {
            get
            {
                if (verbot != null)
                    return AIType.VERBOT;
                return AIType.UNKNOWN;
            }
        }

        public Boolean Quitting
        {
            get { return quitFlag; }
        }

        public bool CreateKnowledgeSet(String hook, String[] kbSet)
        {
            if (EngineType == AIType.VERBOT)
            {
                String verbotPath = System.IO.Path.Combine(Environment.CurrentDirectory,
                    "Content\\Models\\" + hook + "\\Personality");
                String verbotCommonPath = System.IO.Path.Combine(Environment.CurrentDirectory,
                    "Content\\Models\\Common\\Personality");
                String[] vkbFiles = System.IO.Directory.GetFiles(verbotPath, "*.vkb");
                String[] ckbFiles = System.IO.Directory.GetFiles(verbotPath, "*.ckb");
                String[] vkbCommonFiles = System.IO.Directory.GetFiles(verbotCommonPath, "*.vkb");
                String[] ckbCommonFiles = System.IO.Directory.GetFiles(verbotCommonPath, "*.ckb");
                this.state.CurrentKBs.Clear();

                foreach (String file in kbSet)
                {
                    bool bfound = false;
                    // search priority is model personality path
                    // first look in compiled knowledgebases
                    foreach (string kbFile in ckbFiles)
                    {
                        if (file == System.IO.Path.GetFileNameWithoutExtension(kbFile))
                        {
                            verbot.AddCompiledKnowledgeBase(kbFile);
                            state.CurrentKBs.Add(kbFile);
                            bfound = true;
                            break;
                        }
                    }
                    // if not found, look in uncompiled knowledgebases
                    if (!bfound)
                    {
                        foreach (string kbFile in vkbFiles)
                        {
                            if (file == System.IO.Path.GetFileNameWithoutExtension(kbFile))
                            {
                                KnowledgeBase kb = verbot.LoadKnowledgeBase(kbFile);
                                KnowledgeBaseItem kbi = new KnowledgeBaseItem();
                                kbi.Filename = System.IO.Path.GetFileName(kbFile);
                                kbi.Fullpath = System.IO.Path.GetDirectoryName(kbFile) +"\\";
                                verbot.AddKnowledgeBase(kb, kbi);
                                state.CurrentKBs.Add(kbFile);
                                bfound = true;
                                break;
                            }
                        }
                    }
                    // if not found, look in common personality path
                    if (!bfound)
                    {
                        foreach (string kbFile in ckbCommonFiles)
                        {
                            if (file == System.IO.Path.GetFileNameWithoutExtension(kbFile))
                            {
                                verbot.AddCompiledKnowledgeBase(kbFile);
                                state.CurrentKBs.Add(kbFile);
                                bfound = true;
                                break;
                            }
                        }
                    }
                    if (!bfound)
                    {
                        foreach (string kbFile in vkbCommonFiles)
                        {
                            if (file == System.IO.Path.GetFileNameWithoutExtension(kbFile))
                            {
                                KnowledgeBase kb = verbot.LoadKnowledgeBase(kbFile);
                                KnowledgeBaseItem kbi = new KnowledgeBaseItem();
                                kbi.Filename = System.IO.Path.GetFileName(kbFile);
                                kbi.Fullpath = System.IO.Path.GetDirectoryName(kbFile) + "\\";
                                verbot.AddKnowledgeBase(kb, kbi);
                                state.CurrentKBs.Add(kbFile);
                                bfound = true;
                                break;
                            }
                        }
                    }
                }

                return true;
            }
            return false;
        }

        public String GetResponse(String text)
        {
            if (this.EngineType == AIType.VERBOT)
            {
                Reply reply = GetVerbotResponse(text);
                if (reply != null)
                {
                    // strip reply of tags
                    string replyText = "";
                    int start = 0;
                    int end = reply.Text.IndexOf("<");
                    while (end != -1)
                    {
                        replyText += reply.Text.Substring(start, end - start);
                        start = reply.Text.IndexOf(">", end)+1;
                        end = reply.Text.IndexOf("<", start);
                    }
                    if (start < reply.Text.Length)
                        replyText += reply.Text.Substring(start, reply.Text.Length - start);
                    return replyText.Trim();
                }
            }
            return String.Empty;
        }
        private Reply GetVerbotResponse(String text)
        {
            Reply reply = verbot.GetReply(text, state);
            if (reply != null)
            {
                String ret = parseEmbeddedCommands(reply.AgentText);
                if (!String.IsNullOrWhiteSpace(ret))
                    reply.Text += Environment.NewLine + ret;
                if (!String.IsNullOrWhiteSpace(reply.Cmd)) 
                    runProgram(reply.Cmd);
            }
            return reply;
        }

        private String parseEmbeddedCommands(string text)
        {
            string ret = String.Empty;
            string startCommand = "<";
            string endCommand = ">";

            int start = text.IndexOf(startCommand);
            int end = -1;

            while (start != -1)
            {
                end = text.IndexOf(endCommand, start);
                if (end != -1)
                {
                    string command = text.Substring(start + 1, end - start - 1).Trim();
                    if (command != "")
                    {
                        Reply rep = this.runEmbeddedCommand(command);
                        if (!String.IsNullOrWhiteSpace(rep.Text)) ret += rep.Text;
                    }
                }
                start = text.IndexOf(startCommand, start + 1);
            }
            return ret;
        }

        private Reply runEmbeddedCommand(string command)
        {
            if (command.ToLower().IndexOf("learn") == 0)
            {
                // Do special handling of learn here, 
                // "learn" is assumed to occur first in string
                List<KeyValuePair<String, String>> varsList = new List<KeyValuePair<string, string>>();
                int start = command.IndexOf("#");
                int end = -1;
                int separator = -1;
                String varName = String.Empty;
                String varValue = String.Empty;
                char[] trimChars = {'\r', '\n'};

                int fileIndex = -1;
                int childOfIndex = -1;
                int vChildOfIndex = -1;
                int vParentOfIndex = -1;
                int nameIndex = -1;
                int index = 0;

                while (start != -1)
                {
                    string text = command.Substring(start + 1);
                    separator = text.IndexOf(":");
                    if ((end = text.IndexOf("#")) == -1)
                        end = text.Length;

                    varValue = text.Substring(separator + 1, end - separator - 1).Trim(trimChars);
                    varName = text.Substring(0, separator);
                    //vars.Add(varName, varValue);
                    if (varName.ToLower() == "file") fileIndex = index;
                    if (varName.ToLower() == "name") nameIndex = index;
                    if (varName.ToLower() == "childof") childOfIndex = index;
                    if (varName.ToLower() == "vchildof") vChildOfIndex = index;
                    if (varName.ToLower() == "vparentof") vParentOfIndex = index;
                    varsList.Add(new KeyValuePair<string, string>(varName, varValue));

                    if (end == text.Length)
                        start = -1;
                    else
                        start += end + 1;
                    index++;
                }

                // Add rule here
                String vkbFile = String.Empty;
                if (fileIndex != -1)
                    vkbFile = varsList[fileIndex].Value;
                else
                    vkbFile = "Learned.vkb";
                foreach (string stPath in state.CurrentKBs)
                {
                    if (verbot.ContainsCompiledKnowledgeBase(stPath) &&
                        stPath.Contains(vkbFile))
                    {
                        KnowledgeBase kb = (KnowledgeBase)verbot.XmlToolbox.LoadXML(stPath);
                        String ruleID;
                        if (childOfIndex != -1)
                            ruleID = kb.AddRuleChild(kb.GetRuleByNameOrId(varsList[childOfIndex].Value), varsList[nameIndex].Value);
                        else
                            ruleID = kb.AddRule(varsList[nameIndex].Value);
                        Rule rule = kb.GetRule(ruleID);
                        for (int i = 0; i < varsList.Count; i++)
                        {
                            if ((i == fileIndex) || (i == childOfIndex) || (i == nameIndex))
                                continue;
                            if (varsList[i].Key.ToLower() == "input")
                            {
                                String stCondition = String.Empty;
                                try 
                                {
                                    if (varsList[i + 1].Key.ToLower() == "condition")
                                        stCondition = varsList[i + 1].Value; 
                                }
                                catch { stCondition = String.Empty; }

                                rule.AddInput(varsList[i].Value, stCondition);
                            }
                            if (varsList[i].Key.ToLower() == "output")
                            {
                                // assumes "output", "condition", "command" in order
                                String stCondition = String.Empty;
                                String stCommand = String.Empty;
                                try 
                                {
                                    if (varsList[i + 1].Key.ToLower() == "condition")
                                        stCondition = varsList[i + 1].Value;
                                }
                                catch 
                                {
                                    stCondition = String.Empty;
                                }
                                try
                                {
                                    if (varsList[i + 1].Key.ToLower() == "command")
                                        stCommand = varsList[i + 1].Value;
                                    else if (varsList[i + 2].Key.ToLower() == "command")
                                        stCommand = varsList[i + 2].Value;
                                }
                                catch
                                {
                                    stCommand = String.Empty;
                                }
                                rule.AddOutput(varsList[i].Value, stCondition, stCommand);
                            }
                            if (varsList[i].Key.ToLower() == "vchildof")
                            {
                                Rule parentRule = kb.GetRuleByNameOrId(varsList[i].Value);
                                if (parentRule != null)
                                    rule.VirtualParents.Add(parentRule.Id);
                            }
                            if (varsList[i].Key.ToLower() == "vparentof")
                            {
                                Rule childRule = kb.GetRuleByNameOrId(varsList[i].Value);
                                if (childRule != null)
                                    childRule.VirtualParents.Add(rule.Id);
                            }
                        }

                        // update and reload the new KB
                        verbot.XmlToolbox.SaveXML(kb, stPath);
                        KnowledgeBaseItem kbi = new KnowledgeBaseItem();
                        kbi.Filename = System.IO.Path.GetFileName(stPath);
                        kbi.Fullpath = System.IO.Path.GetDirectoryName(stPath) + "\\";
                        verbot.ReloadKnowledgeBase(kb, kbi);

                        break;   
                    }
                }
            }
            else if (command.ToLower().IndexOf("unlearn") == 0)
            {
                // Do special handling of unlearn here, 
                // "unlearn" is assumed to occur first in string
                List<KeyValuePair<String, String>> varsList = new List<KeyValuePair<string, string>>();
                int start = command.IndexOf("#");
                int end = -1;
                int separator = -1;
                String varName = String.Empty;
                String varValue = String.Empty;
                char[] trimChars = { '\r', '\n' };

                int fileIndex = -1;
                int nameIndex = -1;
                int outputIndex = -1;
                int index = 0;

                while (start != -1)
                {
                    string text = command.Substring(start + 1);
                    separator = text.IndexOf(":");
                    if ((end = text.IndexOf("#")) == -1)
                        end = text.Length;

                    varValue = text.Substring(separator + 1, end - separator - 1).Trim(trimChars);
                    varName = text.Substring(0, separator);
                    if (varName.ToLower() == "file") fileIndex = index;
                    if (varName.ToLower() == "name") nameIndex = index;
                    if ((varName.ToLower() == "output") && (outputIndex == -1)) outputIndex = index;
                    varsList.Add(new KeyValuePair<string, string>(varName, varValue));

                    if (end == text.Length)
                        start = -1;
                    else
                        start += end + 1;
                    index++;
                }

                // Remove/update rule here
                String vkbFile = String.Empty;
                if (fileIndex != -1)
                    vkbFile = varsList[fileIndex].Value;
                else
                    vkbFile = "Learned.vkb";
                foreach (string stPath in state.CurrentKBs)
                {
                    if (verbot.ContainsCompiledKnowledgeBase(stPath) &&
                        stPath.Contains(vkbFile))
                    {
                        KnowledgeBase kb = (KnowledgeBase)verbot.XmlToolbox.LoadXML(stPath);
                        Rule rule = kb.GetRuleByNameOrId(varsList[nameIndex].Value);
                        if (rule != null)
                        {
                            if (outputIndex == -1)
                                kb.DeleteRule(rule.Id);
                            else
                            {
                                // just delete certain inputs/outputs
                                for (int i = 0; i < varsList.Count; i++)
                                {
                                    if ((i == fileIndex) || (i == nameIndex))
                                        continue;
                                    if (varsList[i].Key.ToLower() == "input")
                                    {
                                        foreach (Input item in rule.Inputs)
                                        {
                                            if (item.Text == varsList[i].Value)
                                                rule.Inputs.Remove(item);
                                        }
                                    }
                                    if (varsList[i].Key.ToLower() == "output")
                                    {
                                        foreach (Output item in rule.Outputs)
                                        {
                                            if (item.Text == varsList[i].Value)
                                                rule.Outputs.Remove(item);
                                        }
                                    }
                                }
                            }
                        }

                        // update and reload the new KB
                        verbot.XmlToolbox.SaveXML(kb, stPath);
                        KnowledgeBaseItem kbi = new KnowledgeBaseItem();
                        kbi.Filename = System.IO.Path.GetFileName(stPath);
                        kbi.Fullpath = System.IO.Path.GetDirectoryName(stPath) + "\\";
                        verbot.ReloadKnowledgeBase(kb, kbi);

                        break;
                    }
                }
            }
            else
            {
                int spaceIndex = command.IndexOf(" ");

                string function;
                string args;
                if (spaceIndex == -1)
                {
                    function = command.ToLower();
                    args = "";
                }
                else
                {
                    function = command.Substring(0, spaceIndex).ToLower();
                    args = command.Substring(spaceIndex + 1);
                }

                try
                {
                    switch (function)
                    {
                        case "quit":
                        case "exit":
                            quitFlag = true;
                            break;
                        case "run":
                            this.runProgram(args);
                            break;
                        case "send":
                            return this.GetVerbotResponse(args);
                        default:
                            break;
                    }
                }
                catch { }
            }
            return null;
        }

        private void runProgram(string command)
        {
            try
            {
                string[] pieces = this.splitOnFirstUnquotedSpace(command);
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo.FileName = pieces[0].Trim();
                proc.StartInfo.Arguments = pieces[1];
                proc.StartInfo.CreateNoWindow = true;
                proc.StartInfo.UseShellExecute = true;
                proc.Start();
            }
            catch { }
        }

        public string[] splitOnFirstUnquotedSpace(string text)
        {
            string[] pieces = new string[2];
            int index = -1;
            bool isQuoted = false;
            //find the first unquoted space
            for (int i = 0; i < text.Length; i++)
            {
                if (text[i] == '"')
                    isQuoted = !isQuoted;
                else if (text[i] == ' ' && !isQuoted)
                {
                    index = i;
                    break;
                }
            }

            //break up the string
            if (index != -1)
            {
                pieces[0] = text.Substring(0, index);
                pieces[1] = text.Substring(index + 1);
            }
            else
            {
                pieces[0] = text;
                pieces[1] = "";
            }
            return pieces;
        }
    }
}
